44  Python内置函数

44.1 引言Python内置函数的设计哲学

理论背景:函数式编程与内置函数

Python的内置函数(Built-in Functions)体现了语言的实用性(Pragmatism)设计哲学。这些函数无需导入任何模块即可使用,它们构成了Python编程的基础工具箱。从计算机科学的角度,内置函数的设计遵循以下原则:

  1. 高层抽象: 隐藏底层实现细节,提供简洁接口
  2. 泛型设计: 同一函数可应用于多种数据类型
  3. 性能优化: 用C语言实现,比纯Python代码快10-100倍
  4. 一致性: 遵循统一的命名和调用约定

历史背景:内置函数的演进

Python的内置函数集合随着语言发展不断丰富: - Python 1.x (1991-2000): 约50个内置函数,强调基础操作 - Python 2.x (2000-2010): 增加到约70个,引入enumerate(), sorted()等 - Python 3.x (2008-至今): 约80个,增加all(), any(), ascii()

数学分类:内置函数的功能划分

按功能可将内置函数分为以下类别:

类别 代表函数 数量 金融应用场景
数学运算 abs(), round(), sum() 8 收益计算,四舍五入
序列操作 len(), max(), min(), sorted() 12 价格排序,极值查找
迭代器 map(), filter(), zip() 7 数据转换,批量处理
类型转换 int(), float(), str() 15 数据清洗,类型转换
对象操作 isinstance(), id(), hash() 10 类型检查,内存管理
输入输出 print(), input(), open() 5 数据读取,结果输出

补充说明:为什么内置函数用C实现?

sum()函数为例:

# 纯Python实现(慢)
def sum_python(iterable):
    total = 0
    for item in iterable:
        total += item
    return total

# C实现(快)
# 省去了Python字节码解释的开销
# 直接在C层面循环,速度提升10-50倍

性能对比测试(求和100万个整数): - sum_python(): 约50ms - sum(): 约5ms - 性能提升: 10倍

44.2 常用内置函数

平台任务解答代码

以下代码与教学平台任务要求完全一致:

列表 44.1
# 注:该代码块包含未完成的填空代码,需要在平台上完成
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一
stock = ["中信证券","华泰证券","国泰君安","招商证券","中国银河","广发证券","中信建投","国信证券","中金公司","申万宏源"]
print(len(stock)) #计算列表中元素的个数
print(list(enumerate(stock,start=1))) #创建带有索引并且以列表方式分输出


#任务二
profit = [205.39,130.36,98.85,87.69,78.84,78.63,70.47,64.27,61.64,54.75]
stock = ["中信证券","华泰证券","国泰君安","招商证券","中国银河","广发证券","中信建投","国信证券","中金公司","申万宏源"]  # 定义列表stock
profit_total = sum(profit)  #计算10家证券公司的净利润总和
profit_average = profit_total/len(stock)  # 获取数据长度

print("2023年净利润排名前10位的证券公司净利润总和(亿元)",profit_total)  # 输出2023年净利润排名前10位的证券公司净利润总和(
print("2023年净利润排名前10位的证券公司净利润平均数(亿元)",round(profit_average,4))  # 输出2023年净利润排名前10位的证券公司净利润平均数

#任务三
return_Q1 = [-0.0588,-0.0067,-0.0698,0.0239,-0.0059,-0.0672,-0.0739,-0.0193,-0.1543,0.0046]
return_max = max(return_Q1) #找出最大涨幅
return_min = min(return_Q1) #找出最小涨幅
print("2024年1季度股价的最大涨幅",return_max)  # 输出2024年1季度股价的最大涨幅
print("2024年1季度股价的最小涨幅",return_min)  # 输出2024年1季度股价的最小涨幅

price = [18.73,13.61,13.47,13.71,11.76,13.05,21.68,8.11,32.03,4.40]  # 定义列表price
price_sorted = sorted(price) #将股价由小到大排序
print(price_sorted)  # 输出价格数据

#任务四
stock = ["中信证券","华泰证券","国泰君安","招商证券","中国银河","广发证券","中信建投","国信证券","中金公司","申万宏源"]
# 定义列表code
code = ["600030","601688","601211","600999","601881","000776","601066","002736","601995","000166"]
profit = [205.39,130.36,98.85,87.69,78.84,78.63,70.47,64.27,61.64,54.75]  # 定义列表profit
# 定义列表return_Q1
return_Q1 = [-0.0588,-0.0067,-0.0698,0.0239,-0.0059,-0.0672,-0.0739,-0.0193,-0.1543,0.0046]
price = [18.73,13.61,13.47,13.71,11.76,13.05,21.68,8.11,32.03,4.40]  # 定义列表price
print(list(zip(stock,code,profit,return_Q1,price))) #将多个列表中对应的元素打包为一个元组并以列表方式输出

#任务五
fund = 1e7 #H公司的投资资金额
price_huatai = 13.76 #华泰证券股票
N = 100  # 设置期数/数量为100
share = N*int(fund/(price_huatai*N))  # 将数据转换为整数

print("H公司购买华泰证券的股票数量",share)  # 输出H公司购买华泰证券的股票数量
列表 44.2
# 创建数值列表,用于演示各种内置函数
numbers = [1, 2, 3, 4, 5]

# 1. len(): 获取序列长度
# 返回序列中元素的个数
# 适用于字符串、列表、元组、字典等
print(f'长度: {len(numbers)}')
# 输出: 5

# 2. max(): 返回最大值
# 对数值序列,返回最大的数
# 对字符串序列,按Unicode编码比较
print(f'最大值: {max(numbers)}')
# 输出: 5

# 3. min(): 返回最小值
# 与max()类似,返回最小的元素
print(f'最小值: {min(numbers)}')
# 输出: 1

# 4. sum(): 计算序列和
# 从左到右对元素求和
# 注意:sum()只支持数值类型
print(f'求和: {sum(numbers)}')
# 输出: 15 (1+2+3+4+5)

# 5. sorted(): 返回排序后的新列表
# 原序列保持不变
# 默认升序,reverse=True可降序
print(f'排序: {sorted(numbers, reverse=True)}')
# 输出: [5, 4, 3, 2, 1]

# 6. abs(): 返回绝对值
# 对整数或浮点数取绝对值
# 对复数,返回模长
print(f'绝对值: {abs(-10)}')
# 输出: 10

# 7. round(): 四舍五入
# 语法: round(number, ndigits)
# ndigits指定小数位数,默认为0(取整)
print(f'四舍五入: {round(3.14159, 2)}')
# 输出: 3.14

# 8. pow(): 幂运算
# 等价于 x ** y,但支持模运算
# pow(x, y, mod) 等价于 (x ** y) % mod
print(f'幂运算: {pow(2, 3)}')
# 输出: 8

代码深度解析:

  1. len()的时间复杂度:

    # Python对象内部维护长度信息
    # 因此len()的时间复杂度是O(1)
    
    # 列表
    lst = [1, 2, 3]
    # 内部结构: PyObject_VAR_HEAD包含ob_size字段
    print(len(lst))  # 直接读取ob_size,无需遍历
    
    # 字符串
    s = "hello"
    # 同样直接读取长度信息
    print(len(s))  # O(1)
    
    # 但对于生成器等迭代器,len()不适用
    # 需要转换为列表或手动计数
  2. max()/min()的多键排序:

    # 复杂结构的多键比较
    portfolio = [
        {'name': '茅台', 'price': 1850, 'pe': 45},
        {'name': '五粮液', 'price': 220, 'pe': 32},
        {'name': '招商银行', 'price': 45, 'pe': 8}
    ]
    
    # 找出PE最高的股票
    # key参数指定比较依据
    highest_pe = max(portfolio, key=lambda x: x['pe'])
    print(f'PE最高: {highest_pe["name"]}, PE={highest_pe["pe"]}')
    
    # 找出价格最低的股票
    lowest_price = min(portfolio, key=lambda x: x['price'])
    print(f'价格最低: {lowest_price["name"]}, 价格={lowest_price["price"]}')
  3. sorted()的稳定性:

    # Python的排序是稳定的(stable)
    # 即相等元素的相对顺序保持不变
    
    # 先按价格排序
    stocks = [
        ('茅台', 1850, '沪'),
        ('五粮液', 220, '深'),
        ('平安', 45, '深'),
        ('招行', 45, '沪')
    ]
    
    # 按价格升序,价格相同时保持原顺序
    sorted_by_price = sorted(stocks, key=lambda x: x[1])
    print(sorted_by_price)
    # ('平安', 45, '深') 在 ('招行', 45, '沪') 之前
  4. 金融应用:计算统计量:

    # 收益率序列
    returns = [0.05, -0.02, 0.03, 0.07, -0.01]
    
    # 平均收益率
    avg = sum(returns) / len(returns)
    print(f'平均收益率: {avg:.2%}')
    
    # 最大收益和最大亏损
    max_return = max(returns)
    min_return = min(returns)
    print(f'最大收益: {max_return:.2%}')
    print(f'最大亏损: {min_return:.2%}')
    
    # 极差(Range)
    range_ = max(returns) - min(returns)
    print(f'收益极差: {range_:.2%}')
  5. round()的银行家舍入:

    # Python 3使用"银行家舍入"(Round Half to Even)
    # 当正好为0.5时,舍入到最近的偶数
    
    print(round(2.5))  # 2 (最近的偶数)
    print(round(3.5))  # 4 (最近的偶数)
    
    # 这不同于传统的"四舍五入"
    # 原因是减少统计偏差
    
    # 传统四舍五入需要使用decimal模块
    from decimal import Decimal, ROUND_HALF_UP
    print(Decimal('2.5').quantize(Decimal('1'), rounding=ROUND_HALF_UP))  # 3

44.3 高级内置函数

列表 44.3
# 1. map(): 应用函数到可迭代对象的每个元素
# 语法: map(function, iterable, ...)
# 返回一个迭代器,需要用list()转换为列表
prices = ['10.5', '20.3', '15.8']

# 将字符串列表转换为浮点数列表
# float()是内置函数,将字符串转换为浮点数
prices_float = list(map(float, prices))
print(f'转换为浮点数: {prices_float}')
# 输出: [10.5, 20.3, 15.8]

# 2. filter(): 过滤元素
# 语法: filter(function, iterable)
# function返回True时保留元素,False时过滤掉
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 使用lambda表达式定义过滤条件
# lambda x: x % 2 == 0 表示"偶数"
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(f'偶数: {evens}')
# 输出: [2, 4, 6, 8, 10]

# 3. zip(): 配对多个可迭代对象
# 将多个序列对应位置的元素组合成元组
names = ['张三', '李四', '王五']
ages = [25, 30, 35]

# zip()返回迭代器,每个元素是一个元组
pairs = list(zip(names, ages))
print(f'配对: {pairs}')
# 输出: [('张三', 25), ('李四', 30), ('王五', 35)]

# zip()的妙用:字典构造
# 直接将两个列表转换为字典
name_age_dict = dict(zip(names, ages))
print(f'字典: {name_age_dict}')
# 输出: {'张三': 25, '李四': 30, '王五': 35}

# 4. enumerate(): 同时获取索引和元素
# 语法: enumerate(iterable, start=0)
# 返回(索引, 元素)的迭代器
stocks = ['茅台', '五粮液', '招行']

for index, stock in enumerate(stocks, start=1):
    print(f'{index}. {stock}')
# 输出:
# 1. 茅台
# 2. 五粮液
# 3. 招行

代码深度解析:

  1. map()的等价实现:

    # map()的纯Python实现
    def map_python(func, iterable):
        result = []
        for item in iterable:
            result.append(func(item))
        return result
    
    # 使用示例
    numbers = [1, 2, 3, 4, 5]
    squared = list(map(lambda x: x**2, numbers))
    print(squared)  # [1, 4, 9, 16, 25]
    
    # 等价于列表推导式
    squared = [x**2 for x in numbers]
    print(squared)  # [1, 4, 9, 16, 25]
    
    # 列表推导式通常更Pythonic且更易读
  2. filter()的等价实现:

    # filter()的纯Python实现
    def filter_python(func, iterable):
        result = []
        for item in iterable:
            if func(item):  # 如果函数返回True
                result.append(item)
        return result
    
    # 使用示例
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    # 过滤出大于5的数
    greater_than_5 = list(filter(lambda x: x > 5, numbers))
    print(greater_than_5)  # [6, 7, 8, 9, 10]
    
    # 等价于列表推导式
    greater_than_5 = [x for x in numbers if x > 5]
    print(greater_than_5)  # [6, 7, 8, 9, 10]
  3. zip()的配对规则:

    # zip()以最短的序列为准
    list1 = [1, 2, 3, 4, 5]
    list2 = ['a', 'b', 'c']
    
    # 只配对前3个元素
    zipped = list(zip(list1, list2))
    print(zipped)  # [(1, 'a'), (2, 'b'), (3, 'c')]
    
    # zip(*)的反向操作:解包
    matrix = [(1, 'a'), (2, 'b'), (3, 'c')]
    numbers, letters = zip(*matrix)
    print(numbers)  # (1, 2, 3)
    print(letters)  # ('a', 'b', 'c')
  4. 函数式编程应用:

    # 组合使用map()、filter()、reduce()
    
    from functools import reduce  # reduce在Python 3中移到functools
    
    # 场景:计算股票组合的加权收益率
    stocks = [
        {'name': '茅台', 'return': 0.05, 'weight': 0.4},
        {'name': '五粮液', 'return': 0.03, 'weight': 0.3},
        {'name': '招行', 'return': 0.02, 'weight': 0.3}
    ]
    
    # 1. 提取收益率
    returns = list(map(lambda x: x['return'], stocks))
    
    # 2. 提取权重
    weights = list(map(lambda x: x['weight'], stocks))
    
    # 3. 计算加权收益率
    # reduce()对序列进行累积计算
    weighted_return = sum(r * w for r, w in zip(returns, weights))
    print(f'加权收益率: {weighted_return:.2%}')
  5. 金融应用:数据清洗流水线:

    # 原始数据:字符串格式的价格和数量
    raw_data = [
        ('10.5', '100'),
        ('20.3', '200'),
        ('15.8', '150')
    ]
    
    # 使用map()进行数据转换
    prices = list(map(lambda x: float(x[0]), raw_data))
    quantities = list(map(lambda x: int(x[1]), raw_data))
    
    # 计算每笔交易金额
    amounts = list(map(lambda p, q: p * q, prices, quantities))
    print(f'交易金额: {amounts}')
    # [1050.0, 4060.0, 2370.0]
    
    # 过滤出金额大于2000的交易
    large_trades = list(filter(lambda x: x > 2000, amounts))
    print(f'大额交易: {large_trades}')
    # [4060.0, 2370.0]

44.4 金融应用实例投资组合分析

列表 44.4
# 定义投资组合的日收益率序列
# 正值表示上涨,负值表示下跌
returns = [0.05, -0.02, 0.03, 0.07, -0.01]

# 1. 基础统计:平均收益率
# sum()计算总和,len()获取个数
avg_return = sum(returns) / len(returns)
print(f'平均收益率: {avg_return:.2%}')
# 输出: 平均收益率: 2.40%

# 2. 统计正收益天数
# 使用列表推导式过滤正收益
# len()统计数量
positive_returns = [r for r in returns if r > 0]
positive_count = len(positive_returns)
print(f'正收益次数: {positive_count}/{len(returns)}')
# 输出: 正收益次数: 3/5

# 3. 计算最大回撤
# 最大回撤是从峰值到谷底的最大跌幅
# 这是一个重要的风险指标

cum_return = 1.0  # 初始累计收益
max_drawdown = 0   # 最大回撤
peak = 1.0        # 历史峰值

# 遍历每日收益率
for r in returns:
    # 更新累计收益
    # cum_return *= (1 + r) 等价于 cum_return = cum_return * (1 + r)
    cum_return *= (1 + r)

    # 更新历史峰值
    # 如果当前收益超过历史峰值,更新峰值
    if cum_return > peak:
        peak = cum_return

    # 计算当前回撤
    # 回撤 = (峰值 - 当前值) / 峰值
    drawdown = (peak - cum_return) / peak

    # 更新最大回撤
    # max()函数返回两个值中的较大者
    max_drawdown = max(max_drawdown, drawdown)

print(f'最大回撤: {max_drawdown:.2%}')
# 输出: 最大回撤: 2.00%

# 4. 计算夏普比率(简化版)
# 夏普比率衡量单位风险的超额收益
# Sharpe = (平均收益率 - 无风险利率) / 标准差

# 首先计算标准差
import math  # 导入math模块用于sqrt()

# 计算方差
mean = sum(returns) / len(returns)
variance = sum((r - mean) ** 2 for r in returns) / len(returns)

# 标准差是方差的平方根
std_dev = math.sqrt(variance)

# 假设无风险利率为2%(年化)
risk_free_rate = 0.02

# 计算夏普比率(年化)
# 假设252个交易日
sharpe_ratio = (mean * 252 - risk_free_rate) / (std_dev * math.sqrt(252))

print(f'夏普比率: {sharpe_ratio:.2f}')
# 输出某个数值

# 5. 计算收益率的分位数
# 使用sorted()和索引计算
sorted_returns = sorted(returns)
n = len(sorted_returns)

# 中位数(50%分位数)
if n % 2 == 0:
    median = (sorted_returns[n//2 - 1] + sorted_returns[n//2]) / 2
else:
    median = sorted_returns[n//2]

print(f'中位数收益率: {median:.2%}')

# 25%分位数(下四分位数)
q25_index = int(n * 0.25)
q25 = sorted_returns[q25_index]
print(f'25%分位数: {q25:.2%}')

# 75%分位数(上四分位数)
q75_index = int(n * 0.75)
q75 = sorted_returns[q75_index]
print(f'75%分位数: {q75:.2%}')

代码深度解析:

  1. 最大回撤的计算原理:

    # 最大回撤是评估策略风险的关键指标
    # 它回答了"从最高点到最低点,最多亏损多少?"
    
    # 可视化理解
    # 假设累计收益曲线如下:
    # 1.0 -> 1.05 -> 1.029 -> 1.0599 -> 1.1341 -> 1.1228
    #        ↑峰值   ↓回撤2%    ↑新峰值   ↑新峰值  ↓回撤
    
    # 计算步骤:
    # 1. 初始峰值 = 1.0
    # 2. 第1天: 1.0 * 1.05 = 1.05, 新峰值 = 1.05
    # 3. 第2天: 1.05 * 0.98 = 1.029, 回撤 = (1.05-1.029)/1.05 ≈ 2%
    # 4. 第3天: 1.029 * 1.03 = 1.0599, 新峰值 = 1.0599
    # 5. 第4天: 1.0599 * 1.07 = 1.1341, 新峰值 = 1.1341
    # 6. 第5天: 1.1341 * 0.99 = 1.1228, 回撤 = (1.1341-1.1228)/1.1341 ≈ 1%
    
    # 最终最大回撤 = max(2%, 1%) = 2%
  2. 标准差的数学含义:

    # 标准差衡量数据的离散程度
    # 在金融中,标准差代表波动率,是风险的度量
    
    # 计算公式:
    # σ = sqrt(Σ(xi - μ)² / n)
    
    # 其中:
    # - xi: 第i个收益率
    # - μ: 平均收益率
    # - n: 样本数量
    
    # 手动计算示例
    returns = [0.05, -0.02, 0.03, 0.07, -0.01]
    mean = sum(returns) / len(returns)  # 0.024
    
    # 计算每个收益率与均值的偏差平方
    squared_deviations = [(r - mean) ** 2 for r in returns]
    
    # 方差是偏差平方的平均
    variance = sum(squared_deviations) / len(returns)
    
    # 标准差是方差的平方根
    std_dev = variance ** 0.5
    print(f'标准差(波动率): {std_dev:.4f}')
  3. 分位数的应用:

    # 分位数描述数据的分布特征
    
    # 在投资组合中的应用
    # 假设有100只股票的收益率
    import random
    stock_returns = [random.uniform(-0.1, 0.2) for _ in range(100)]
    
    # 找出表现最好的10%股票
    sorted_returns = sorted(stock_returns, reverse=True)
    top_10_percent = sorted_returns[:10]
    
    print(f'前10%股票的平均收益率: {sum(top_10_percent)/len(top_10_percent):.2%}')
    
    # 找出表现最差的10%股票
    bottom_10_percent = sorted_returns[-10:]
    print(f'后10%股票的平均收益率: {sum(bottom_10_percent)/len(bottom_10_percent):.2%}')
  4. 性能优化:使用NumPy替代内置函数:

    # 对于大规模数值计算,NumPy比内置函数快得多
    
    import numpy as np
    import time
    
    # 生成大规模数据
    large_returns = [random.random() for _ in range(1000000)]
    
    # 方法1:使用内置函数
    start = time.time()
    mean_python = sum(large_returns) / len(large_returns)
    std_python = (sum((x - mean_python) ** 2 for x in large_returns) / len(large_returns)) ** 0.5
    print(f'Python方法: {time.time() - start:.4f}秒')
    
    # 方法2:使用NumPy
    start = time.time()
    returns_array = np.array(large_returns)
    mean_numpy = returns_array.mean()
    std_numpy = returns_array.std()
    print(f'NumPy方法: {time.time() - start:.4f}秒')
    
    # NumPy通常快10-100倍
  5. all()和any()函数的应用:

    # all():所有元素为True时返回True
    # any():任一元素为True时返回True
    
    # 应用1:检查所有收益率是否为正
    returns = [0.01, 0.02, 0.03, -0.01, 0.02]
    all_positive = all(r > 0 for r in returns)
    print(f'所有收益为正: {all_positive}')  # False
    
    # 应用2:检查是否有任何收益率为负
    any_negative = any(r < 0 for r in returns)
    print(f'存在负收益: {any_negative}')  # True
    
    # 应用3:风险控制
    # 检查是否所有持仓的权重之和为1
    weights = [0.3, 0.4, 0.3]
    weights_sum = sum(weights)
    print(f'权重归一化: {abs(weights_sum - 1.0) < 1e-10}')  # True

最佳实践总结:

  1. 优先使用内置函数而非自己实现:

    • 内置函数用C实现,性能更好
    • 代码更简洁,可读性更高
    • 经过充分测试,可靠性更强
  2. 函数式编程 vs 列表推导式:

    # map/filter:函数式风格
    result = list(map(lambda x: x**2, numbers))
    
    # 列表推导式:更Pythonic
    result = [x**2 for x in numbers]
    
    # 推荐:列表表导式更易读
  3. 性能优化建议:

    • 大规模数值计算 → 使用NumPy
    • 复杂逻辑判断 → 使用列表推导式或生成器
    • 需要延迟计算 → 使用迭代器而非列表
  4. 常见陷阱:

    # 陷阱1:修改正在遍历的列表
    lst = [1, 2, 3, 4, 5]
    for item in lst:
        if item % 2 == 0:
            lst.remove(item)  # 危险!可能跳过元素
    
    # 正确做法:创建新列表
    lst = [1, 2, 3, 4, 5]
    lst = [x for x in lst if x % 2 != 0]
    
    # 陷阱2:在可变默认参数中使用mutable对象
    def foo(lst=[]):  # 危险!
        lst.append(1)
        return lst
    
    # 正确做法
    def foo(lst=None):
        if lst is None:
            lst = []
        lst.append(1)
        return lst